import urllib
import random
from google.appengine.api import users
from google.appengine.api import urlfetch
from models.model import *
from updateInfo import * 

def buyForMonkey(monkeyNum):
    """ Get Virtual Investor id and purchase stocks based on the Virtual Investor strategy"""

    # Number of stocks to buy for monkey:
    buyStocksNum=5
    
    # Creating query string based on Investor strategy
    if monkeyNum==1: 
        q_query="SELECT * FROM StocksData ORDER BY precentChange DESC LIMIT 0,%d" % buyStocksNum
    elif monkeyNum==2: 
        q_query="SELECT * FROM StocksData ORDER BY stockcounter DESC LIMIT 0,%d" % buyStocksNum
    elif monkeyNum==3: 
        q_query="SELECT * FROM StocksData"
    else:
        return
    
    # Return the Virtual Investor Google user   
    user = users.User(getMonkeyName(monkeyNum))
    
    # Sell Virtual Investor stocks
    q_0 = db.GqlQuery("SELECT * FROM Stocks WHERE buyer = :1",user)
    for stock in q_0:
        sellStock(user,str(stock.key()))
        
    # Fetches user from database    
    trenuser = getCurrentUser(user)
    
    # Buy stocks for Virtual Investor new stocks based on the query string 
    q_1 = db.GqlQuery(q_query)
    rowCount=q_1.count(buyStocksNum)
    
    # There are no stocks in database (exit function)
    if rowCount==0:
        return
        
    # Divide the investment amount between all stocks
    amount = round(trenuser.bankamount/rowCount,2)
    numList=[]
    i=rowCount-1
    q_1=q_1.fetch(buyStocksNum)
    # Buy inverstor stocks
    while i>=0:
        if monkeyNum!=3:
            stock=q_1[i]
        else:
            currRandInt=random.randint(0,len(q_1)-1)
            while numList.count(currRandInt)>0:
                currRandInt=random.randint(0,len(q_1)-1)
            numList.append(currRandInt)
            stock=q_1[currRandInt]
        if i==0:
            trenuser = getCurrentUser(user)
            amount = trenuser.bankamount
        buyStock(user,stock.stockname,amount)
        i=i-1
        
def getCategoryRef(setext):
    """ Parses main category from I4S based on stock name """
    
    # Retrieve category name from Google Insights
    try:
        url="http://www.google.com/insights/search/overviewReport?q=%s#" % (urllib.quote(setext))
    except:
        # Return -1, meaning wrong stock charcters
        return -1
    result = urlfetch.fetch(url=url, headers={'Cookie': getSID()})
    
    # init category name
    CategoryName=""
    # If page was download successfully , parse it, and fetch the main category for the stock
    if result.status_code==200:
        start=result.content.find("Categories:&nbsp;")
        end=result.content.find(" (", start)
        ExtrPart=result.content[start:end]
        CategoryName=ExtrPart[ExtrPart.find(">")+1:]
        # Finish Retrieval of category name
        
    # If no category name was found, set name to "Other"        
        if CategoryName=="":
            CategoryName="Other"
        
    # Retrieve category from StocksCategories atomically by transaction
    return StocksCategories.get_or_insert(CategoryName, category=CategoryName)

def createETF(categoryRefernce):
        """ Create new ETF (update the stocks list in each ETF) """
        
        q_1 = db.GqlQuery("SELECT * FROM ETFData WHERE categoryref = :1", categoryRefernce)
        if q_1.count()==0:
            currentETF= ETFData()
        else:
            q_1.fetch(1)
            currentETF= q_1[0]
            
        # Creating a new ETF according to top 5 stocks strategy
        q_2 = db.GqlQuery("SELECT * FROM StocksData WHERE categoryref = :1  ORDER BY precentChange DESC LIMIT 0,5", categoryRefernce)
        currentETF.stocklist=[]
        for stock in q_2:
            currentETF.stocklist.append(stock.key())
        currentETF.precentChange=1.0
        currentETF.categoryref=categoryRefernce
        currentETF.put()
        return currentETF        
        

def valideStockName(setext):
    """ Validate stock name """
    if len(setext)==0 or len(setext)>maxStockLength or setext=="Stock Name (In English)":
        return -1
    else:
        return 1
                
def getStockRef(setext):
    """ Get stock key from datastore if exists, otherwise create a new one"""
    setext=setext.replace("'","")
    setext=setext.replace("'","")
    setext=setext.replace("\"","")
    setext=setext.strip()
    setext=setext.lower()
    # If stock wasn't in database, then check if category is in StocksCategories (create otherwise)
    if valideStockName(setext)==-1:
        return -1
    # Get category key (or create) from datastore
    res=getCategoryRef(setext)
    if res==-1:
        return -1
    else:
        # Retrieve stock from stocksdata atomically by transaction
        return StocksData.get_or_insert(setext, precentChange=1.0, stockname=setext,categoryref=res,cronupdatedate=datetime.date.today())
    
def ETFChange():
    """ Use CreateETF() function to update content of ALL ETFs"""
    
    #Select ETF from datastore
    q_2 = db.GqlQuery("SELECT * FROM ETFData")
    
    for current_ETF in q_2:
        createETF(current_ETF.categoryref)
        
def deposit(trenduser, amount):
    """ Deposit amount in user bank """
    trenduser.bankamount+=round(amount,2)
    trenduser.put()
        
        
def depositAtomic(sellAmount, trenduserKey):
        """ Increase amount of user bank amount """
        try:
            trenduser=db.get(trenduserKey)
        except:
            return -1
        
        # Deposit amount in user bank
        trenduser.bankamount+=round(sellAmount,2)
        trenduser.put()
        return 1
        
def stockDecCounter(stock):
    """ Decrease counter of stock holders in StocksData """
    stock.stockcounter-=1
    stock.put()
    return 1
        
def withrawAmount(trenduserKey, value):
    """ Withdraw amount back to user bank amount """
    # Decrease bank account money
    trenduser=db.get(trenduserKey)
    trenduser.bankamount-=round(round(value,2),2)
    trenduser.put()
    return 1

        
def sellStock(user,sells):
    """ Sell stock of user with key - sells""" 
    q_1 = sells

    # Check if user is the real owner of this stock, and if stock exists
    if not q_1 or q_1.buyer!=user:
        return 0
        
    # Fetching user from data base and updating his bank account
    trenduser=getCurrentUser(user)
    
    result =db.run_in_transaction(depositAtomic, q_1.currValue, trenduser.key())
    if result <1 :
        return result
    # Bank amount has been increased, in case of error need to withdraw money
    
    tempRef=q_1.stockref
    
    # Put this sell in news feed
    moveToNewsFeed(q_1,tempRef, 1) 
    
    
    if db.run_in_transaction(stockDecCounter,tempRef)!=1:
        withrawAmount(trenduser.key(), q_1.currValue)
        return -1
        
    
    # Delete stock log
    q_1.delete()
    
    # Delete stock data if needed
    deleteFromStockDb(tempRef)
    deleteFromStockCategoryDb(tempRef.categoryref)
    
    return 1
    
def sellETF(user, ETFstock):
    """ Sell stock of user with key - ETFstock
    """ 
    # Check if user is the real owner of this stock, and if stock exists
    if not ETFstock or ETFstock.buyer!=user:
        return 0

    # Fetching user from data base and updating his bank account
    trenduser=getCurrentUser(user)

    result =db.run_in_transaction(depositAtomic, ETFstock.currValue, trenduser.key())
    if result <1 :
        return result

    # Keeping refernce to ETFstock list (basket) in order to delete all the stocks
    ETFref=ETFstock.basketref
    listOfStocks=ETFref.stocklist
    
    # Move to NewsFeeds
    moveToNewsFeed(ETFstock,ETFref, 1)
    
    # deleting stock from stockData if no other stock or ETF is attach to it
    # we delete the stock only if no other stock from Stocks points to it and no other-
    # user contain the same ETF
    if ETFref.stocks_set.count()==1:
        for stockReference in listOfStocks:
            #deleteFromStockDb(db.get(stockReference))
            currObject=db.get(stockReference)
            if currObject.stocks_set.count()==0 :
                currObject.delete()
    
    tempRef=ETFstock
    # Deleting ETFStock from Stocks
    ETFstock.delete()
    
    # Delete category from StocksCategories if no other stock or ETF is attach to it
    deleteFromStockCategoryDb(tempRef.basketref.categoryref)

    # Deleting ETFStock from ETFData
    deleteFromETFDb(tempRef.basketref)
    
    return 1

def deleteFromETFDb(ETFstockRef):
    """ Delete ETF from DB """
    if ETFstockRef.stocks_set.count()==0:
        ETFstockRef.delete()
    
def deleteFromStockDb(stockreference):
    """ Delete Stock from datastore """
    q_2 = db.GqlQuery("SELECT * FROM ETFData WHERE categoryref = :1 LIMIT 0,1", stockreference.categoryref)
    
    if stockreference.stocks_set.count()==0 and stockreference.arenacounter<=0:
        if q_2.count()>0:
            flag=0
            for stockKey in q_2[0].stocklist:
                stockNameRef=db.get(stockKey)
                if (stockNameRef.stockname==stockreference.stockname):
                    flag=1
            if(flag==0):
                stockreference.delete()
        else:
            stockreference.delete()
            
def deleteFromStockCategoryDb(categoryreference):
    """ Delete Category from datastore """
    if categoryreference.stocksdata_set.count()==0:
        categoryreference.delete()
        
def validateAndPurchase(stockName, purchaseAmount, trenduserKey):
        """ Validate amount of purchase and decrease bank amount """
        trenduser=db.get(trenduserKey)
        if valideStockName(stockName)==-1:
            return -1, purchaseAmount
                    
        # Check for valid float stock value
        try:
            purchaseAmount=round(float(purchaseAmount),2)
        except:
            # Return -2, meaning stock value is not a number/float
            return -2, purchaseAmount
            
        if purchaseAmount<=0:
            # Return -1, meaning stock value is not positive
            return -2, purchaseAmount
            
        # Check if user has enough money in bank account            
        elif trenduser.bankamount<purchaseAmount:
            # Return 0, meaning not enough money in bank account
            return 0, purchaseAmount
        else:
            trenduser.bankamount-=round(round(purchaseAmount,2),2)
            trenduser.put()
            return 1, purchaseAmount
        
def initStock(stock, user, amount):
    """ Set stock amount and trenduser information """
    stock.buyer = user
    stock.buyValue = amount
    stock.currValue=stock.currValueBase=stock.currValueNotRounded=stock.buyValue
    # Set buying date to today
    stock.buydate=datetime.datetime.now()
    
def refundAmount(trenduserKey, value):
    """ Refund amount back to user bank amount """
    # Decrease bank account money
    trenduser=db.get(trenduserKey)
    trenduser.bankamount+=round(round(value,2),2)
    trenduser.put()
    return 1

    
def stockIncCounter(stock):
    """ Increase counter of stock holders in StocksData """
    stock.stockcounter+=1
    stock.put()
    return 1
    
# Buy user stock function            
def buyStock(user, setext, sevalue):
        """ Buy stock with name se text in value of sevalue to user """

        #create user and stocks Objects
        stockbuy = Stocks() 
        
        #retrieving current user from data base 
        trenduser=getCurrentUser(user)
        
        result, sevalue =db.run_in_transaction(validateAndPurchase,setext, sevalue, trenduser.key())
        if result <1 :
            return result
        # Bank amount has been decreased, in case of error need to refund money
        
        # Initialize stock with: purchase amount, trenduser information    and buying date
        initStock(stockbuy, user, sevalue)
        
        # Get stockref (and categoryref)

        res=getStockRef(setext)
        if res==-1:
            refundAmount(trenduser.key(), sevalue)
            return -1
        else:    
            stockbuy.stockref=res
        
        if db.run_in_transaction(stockIncCounter,stockbuy.stockref)!=1:
            refundAmount(trenduser.key(), sevalue)
            return -1
        
        stockbuy.categoryref=stockbuy.stockref.categoryref
        stockbuy.put()
            
        # Move for news feed
        moveToNewsFeed(stockbuy,res, 0)
            
        # Return 1, meaning buying succeeded
        return 1
               
def initETF(etf, categoryRefernce):
        """ Initialize existing ETF list of stocks with the most leading stocks at data base """ 

        currentETF= etf
        #creating a new ETF list according to top 5 stocks strategy
        q_2 = db.GqlQuery("SELECT * FROM StocksData WHERE categoryref = :1  ORDER BY precentChange DESC LIMIT 0,5", categoryRefernce)
        currentETF.stocklist=[]
        for stock in q_2:
            currentETF.stocklist.append(stock.key())
        currentETF.precentChange=1.0
        currentETF.categoryref=categoryRefernce
        currentETF.put()
        return currentETF    


def buyETF(user, categoryKey, amount):
    """ Buy ETF with categoryKey with amount to user """

    # Creating new stock and updating it with ETF reference
    stockbuy = Stocks() 
    
    # Retrieving  user from the dataBase
    trenduser=getCurrentUser(user)
    
    result, amount =db.run_in_transaction(validateAndPurchase,categoryKey, amount, trenduser.key())
    if result <1 :
        return result
    
    # Initialize stock with purchase amount and trenduser information    
    initStock(stockbuy, user, amount)
    
    # Retrieve category reference from StocksCategories
    q_1 = db.GqlQuery("SELECT * FROM StocksCategories WHERE category = :1 LIMIT 0,1", categoryKey)
    # No key found
    if q_1.count()==0:
        return -1    
    q_1=q_1.fetch(1)
    stockbuy.categoryref=q_1[0]
              
    # Getting a reference to ETFData
    stockbuy.basketref=ETFData.get_or_insert(categoryKey)
    initETF(stockbuy.basketref, stockbuy.categoryref)
     
    # ETFstock in data base        
    stockbuy.put()
        
    # Move for newsfeed
    moveToNewsFeed(stockbuy,stockbuy.basketref, 0)
        
    # Return 1, meaning buying succeeded
    return 1